#include <nxos.h>

NX_U32 t0(void * arg)
{
    NX_Printf("uthread %p start\n", arg);
    while (1)
    {
        /* code */
        NX_Printf("uthread %p running...\n", arg);
        NX_ThreadSleep(500);
        //NX_ThreadExit(1);
        return 2;
    }
    return 0;
}

NX_PRIVATE void TestCreate(void)
{
    NX_ThreadAttr attr = NX_THREAD_ATTR_INIT;
    NX_Solt tsolt = NX_SOLT_INVALID_VALUE;
    NX_Error err;

    NX_Printf("attr: stack size:%d, sched priority:%d", attr.stackSize, attr.schedPriority);

    NX_ThreadAttrInit(&attr, 4096, NX_THREAD_PRIORITY_LOW);

    NX_Printf("attr: stack size:%d, sched priority:%d", attr.stackSize, attr.schedPriority);

    int i = 0;
    while (i++ < 256)
    {
        tsolt = NX_ThreadCreate(&attr, t0, (void *) 0x1234abcd, NX_THREAD_CREATE_SUSPEND);
        err = NX_ErrorGet();
        NX_Printf("thread %d solt:%d\n", err, tsolt);
        NX_ThreadResume(tsolt);
    }
    
    NX_ThreadSleep(5000);

    NX_Printf("test create done\n");
}

NX_U32 t1(void * arg)
{
    NX_Printf("uthread %p start\n", arg);
    NX_ThreadSleep(100);
    NX_Printf("uthread %p exit...\n", arg);
    return 12345678;
}

NX_PRIVATE void TestWait(void)
{
    NX_ThreadAttr attr = NX_THREAD_ATTR_INIT;
    NX_Solt tsolt = NX_SOLT_INVALID_VALUE;
    NX_Error err;
    NX_U32 exitCode;

    int i;
    for (i = 0; i < 48; i++)
    {
        tsolt = NX_SOLT_INVALID_VALUE;
        tsolt = NX_ThreadCreate(&attr, t1, (void *) 1, 0);
        NX_Printf("thread %d solt:%d\n", NX_ErrorGet(), tsolt);
        exitCode = 0;
        err = NX_ThreadWait(tsolt, &exitCode);
        NX_Printf("thread wait:%d e:%d\n", err, exitCode);
    }
}

NX_U32 t2(void * arg)
{
    NX_Printf("uthread %p start\n", arg);
    
    while (1)
    {
    }

    return 0;
}

NX_PRIVATE void TestTerminate(void)
{
    NX_Solt tsolt = NX_SOLT_INVALID_VALUE;
    NX_Error err;
    NX_U32 exitCode;

    int i;
    for (i = 0; i < 48; i++)
    {
        tsolt = NX_SOLT_INVALID_VALUE;
        tsolt = NX_ThreadCreate(NX_NULL, t2, (void *) 1, 0);
        NX_Printf("thread %d solt:%d\n", NX_ErrorGet(), tsolt);
        
        NX_ThreadSleep(100);
        exitCode = i;
        err = NX_ThreadTerminate(tsolt, exitCode);
        NX_Printf("thread term:%d\n", err);
    }
}

NX_PRIVATE void TestId(void)
{
    NX_Solt solt;
    NX_Error err;
    NX_U32 outId;
    
    NX_U32 tid = NX_ThreadGetCurrentId();    
    NX_Printf("tid: %d\n", tid);

    solt = NX_SOLT_INVALID_VALUE;
    solt = NX_ThreadGetCurrent();
    err = NX_ErrorGet();
    NX_Printf("current: solt:%d, err:%d\n", solt, err);

    outId = NX_ThreadGetId(solt);
    err = NX_ErrorGet();
    NX_Printf("get tid:%d solt:%d, err:%d\n", outId, solt, err);

    outId = NX_ThreadGetProcessId(solt);
    err = NX_ErrorGet();
    NX_Printf("get process id:%d solt:%d, err:%d\n", outId, solt, err);
}

/* Producer And Consumer test */
#define PC_TEST_TIME 10 /* 10 s */

#define MAX_FULL_SEM_VAL 10

/* config */
#define USE_SEM_MUTEX 0

NX_PRIVATE NX_Solt PC_Mutex; /* mutex */

NX_PRIVATE NX_Solt PC_Full; /* sem */
NX_PRIVATE NX_Solt PC_Empty; /* sem */

NX_PRIVATE int goods = 0;

NX_PRIVATE NX_U32 ProducerThread(void *arg)
{
    int temp = 0;
    while (1)
    {
        /* make goods */
        temp++;

        NX_Printf("make goods: %d\n", temp);

        NX_SemaphoreWait(PC_Empty);

#if USE_SEM_MUTEX == 1
        NX_SemaphoreWait(PC_Mutex);
#else
        NX_MutexAcquire(PC_Mutex);
#endif

        /* put goods */
        goods = temp;

#if USE_SEM_MUTEX == 1
        NX_SemaphoreSignal(PC_Mutex);
#else
        NX_MutexRelease(PC_Mutex);
#endif

        NX_SemaphoreSignal(PC_Full); /* add a goods */
    }
    return 0;
}

NX_PRIVATE NX_U32 ConsumerThread(void *arg)
{
    int temp = 0;

    while (1)
    {
        NX_SemaphoreWait(PC_Full);

#if USE_SEM_MUTEX == 1
        NX_SemaphoreWait(PC_Mutex);
#else
        NX_MutexAcquire(PC_Mutex);
#endif

        /* get goods */
        temp = goods;

#if USE_SEM_MUTEX == 1
        NX_SemaphoreSignal(PC_Mutex);
#else
        NX_MutexRelease(PC_Mutex);
#endif

        NX_SemaphoreSignal(PC_Empty); /* del a goods */
        
        /* use goods */
        NX_Printf("use goods: %d\n", temp);

    }
    return 0;
}

NX_PRIVATE void ProducerAndConsumer(void)
{
    int sleepCount = 0;
    NX_Error err;
    
    volatile int PC_State;

#if USE_SEM_MUTEX == 1
    PC_Mutex = NX_SemaphoreCreate(1);
#else
    PC_Mutex = NX_MutexCreate(0);
#endif    

    PC_Empty = NX_SemaphoreCreate(MAX_FULL_SEM_VAL);
    PC_Full = NX_SemaphoreCreate(0);
    
    NX_Printf("mutex sem:%p\n", PC_Mutex);
    NX_Printf("empty sem:%p\n", PC_Empty);
    NX_Printf("full sem:%p\n", PC_Full);

    PC_State = 0;

    NX_Solt producer = NX_SOLT_INVALID_VALUE;
    producer = NX_ThreadCreate(NX_NULL, ProducerThread, NX_NULL, NX_THREAD_CREATE_SUSPEND);
    if (NX_ErrorGet() != NX_EOK)
    {
        NX_Printf("create producer error!\n");
        return;
    }

    NX_Solt consumer = NX_SOLT_INVALID_VALUE;
    consumer = NX_ThreadCreate(NX_NULL, ConsumerThread, NX_NULL, NX_THREAD_CREATE_SUSPEND);
    if (NX_ErrorGet() != NX_EOK)
    {
        NX_Printf("create consumer error!\n");
        return;
    }

    NX_ThreadResume(producer);
    NX_ThreadResume(consumer);
    
    while (PC_State != 1)
    {
        NX_Printf("sleep 1s\n");
        NX_ThreadSleep(1000); /* sleep 1s */
        sleepCount++;
        if (sleepCount == PC_TEST_TIME)
        {
            err = NX_ThreadTerminate(producer, 0);
            NX_Printf("terminate thread %d, err state: %s\n", producer, NX_ErrorToString(err));
            err = NX_ThreadTerminate(consumer, 0);
            NX_Printf("terminate thread %d, err state: %s\n", consumer, NX_ErrorToString(err));
        }
        if (sleepCount == PC_TEST_TIME + 1)
        {
            PC_State++;
        }
    }
    NX_Printf("test ProducerAndConsumer ok!\n");
}

NX_PRIVATE void TestSemaphore(void)
{
    NX_Error err;
    NX_Solt solt0;
    NX_IArch val;
    solt0 = NX_SemaphoreCreate(1);
    NX_Printf("sem:%d err:%d\n", solt0, NX_ErrorGet());

    val = NX_SemaphoreGetValue(solt0);
    NX_Printf("sem val:%d\n", val);

    err = NX_SemaphoreWait(solt0);
    NX_Printf("sem wait e:%d\n", err);

    err = NX_SemaphoreSignal(solt0);
    NX_Printf("sem signal e:%d\n", err);

    err = NX_SemaphoreTryWait(solt0);
    NX_Printf("sem trt wait e:%d\n", err);

    err = NX_SemaphoreSignalAll(solt0);
    NX_Printf("sem signal all e:%d\n", err);

    val = NX_SemaphoreGetValue(solt0);
    NX_Printf("sem val:%d\n", val);

    err = NX_SemaphoreDestroy(solt0);
    NX_Printf("sem destroy e:%d\n", err);

    solt0 = NX_SemaphoreCreate(1);
    NX_Printf("sem:%d\n", solt0);

    err = NX_SoltClose(solt0);
    NX_Printf("sem close e:%d\n", err);

    ProducerAndConsumer();
}


NX_PRIVATE NX_VOLATILE int threadTick = 0;

NX_PRIVATE NX_U32 TestThread1(void *arg)
{
    NX_Printf("Hello, test thread 1: %p\n", arg);
    int i = 0;
    while (1)
    {
        i++;
        if (i > 100)
        {
            threadTick++;
            NX_ThreadExit(0);
        }
    }
    return 0;
}

NX_PRIVATE NX_U32 TestThread2(void *arg)
{
    NX_Printf("Hello, test thread 2: %p\n", arg);

    int i = 0;
    while (1)
    {
        i++;
        if (i > 100)
        {
            threadTick++;
            break;
        }
    }
    NX_Printf("thread exit: %d\n", NX_ThreadGetCurrentId());
    return 0;
}

NX_PRIVATE NX_U32 TestThread3(void *arg)
{
    NX_Printf("Hello, test thread 3: %p\n", arg);
    
    /* wait terminate */
    while (1)
    {
        
    }
    return 0;
}

NX_PRIVATE NX_Solt thread3Solt;

NX_PRIVATE NX_U32 TestThread4(void *arg)
{
    NX_Printf("Hello, test thread 4: %p\n", arg);
    int i = 0;
    while (1)
    {
        i++;
        if (i == 100)
        {
            NX_Printf("terminate thread: %d\n", thread3Solt);
            NX_ThreadTerminate(thread3Solt, 0);
        }
        if (i == 1000)
        {
            threadTick++;
            return 0;
        }
    }
    return 0;
}

NX_PRIVATE NX_Solt mutexLock;

NX_PRIVATE NX_U32 TestMutex1(void *arg)
{
    int i = 0;
    while (1)
    {
        i++;
        if (i > 100)
        {
            threadTick++;
            return 0;
        }
        NX_MutexAcquire(mutexLock);
        NX_Printf("thread:%d get lock\n", NX_ThreadGetCurrentId());
        NX_MutexRelease(mutexLock);
        NX_ThreadSleep(10);
    }
    return 0;
}

NX_PRIVATE NX_Error TestThreadMutex(void)
{
    /* thread */
    threadTick = 0;
    NX_Solt thread = NX_SOLT_INVALID_VALUE;

    thread = NX_ThreadCreate(NX_NULL, TestThread1, (void *) 0x1234abcd, 0);
    /* make sure has enough memory */
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    thread = NX_ThreadCreate(NX_NULL, TestThread2, (void *) 0x1234abcd, 0);
    /* make sure has enough memory */
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    thread = NX_ThreadCreate(NX_NULL, TestThread3, (void *) 0x1234abcd, 0);
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    thread3Solt = thread;
    
    thread = NX_ThreadCreate(NX_NULL, TestThread4, (void *) 0x1234abcd, 0);
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    while (threadTick != 3);

    /* mutex */
    threadTick = 0;

    mutexLock = NX_MutexCreate(0);
    NX_ThreadCreate(NX_NULL, TestMutex1, NX_NULL, 0);
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    NX_ThreadCreate(NX_NULL, TestMutex1, NX_NULL, 0);
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    NX_ThreadCreate(NX_NULL, TestMutex1, NX_NULL, 0);
    if (NX_ErrorGet() != NX_EOK)
    {
        return NX_ENOMEM;
    }

    while (threadTick != 3);

    NX_Printf("thread test done.\n");
    return NX_EOK;
}


NX_PRIVATE void TestMutex(void)
{
    NX_Error err;
    NX_Solt solt0;

    solt0 = NX_MutexCreate(0);
    NX_Printf("mutex:%d\n", solt0);

    err = NX_MutexAcquire(solt0);
    NX_Printf("mutex acquire e:%d\n", err);

    err = NX_MutexAcquirable(solt0);
    NX_Printf("mutex acquirable:%d\n", err);

    err = NX_MutexRelease(solt0);
    NX_Printf("mutex release e:%d\n", err);

    err = NX_MutexAcquirable(solt0);
    NX_Printf("mutex acquirable:%d\n", err);

    err = NX_MutexTryAcquire(solt0);
    NX_Printf("mutex try acquire e:%d\n", err);

    err = NX_MutexRelease(solt0);
    NX_Printf("mutex release e:%d\n", err);

    err = NX_MutexDestroy(solt0);
    NX_Printf("mutex destroy e:%d\n", err);

    solt0 = NX_MutexCreate(NX_MUTEX_ATTR_LOCKED);
    NX_Printf("mutex:%d\n", solt0);

    err = NX_SoltClose(solt0);
    NX_Printf("mutex close e:%d\n", err);

    TestThreadMutex();
}

NX_Error TEST_Thread(char *cmdline)
{
    TestCreate();
    TestWait();
    TestTerminate();
    TestId();
    return NX_EOK;
    TestSemaphore();
    TestMutex();
    return NX_EOK;
}
